/**
 * 
 */
package hu.gbalage.owlforms.impl.data;

import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.semanticweb.owl.inference.OWLReasoner;
import org.semanticweb.owl.inference.OWLReasonerException;
import org.semanticweb.owl.model.AddAxiom;
import org.semanticweb.owl.model.OWLClass;
import org.semanticweb.owl.model.OWLConstant;
import org.semanticweb.owl.model.OWLDataFactory;
import org.semanticweb.owl.model.OWLDataProperty;
import org.semanticweb.owl.model.OWLDataType;
import org.semanticweb.owl.model.OWLIndividual;
import org.semanticweb.owl.model.OWLObjectProperty;
import org.semanticweb.owl.model.OWLOntology;
import org.semanticweb.owl.model.OWLOntologyChange;

import hu.gbalage.owlforms.Constants;
import hu.gbalage.owlforms.OWLFormsManager;
import hu.gbalage.owlforms.api.Field;
import hu.gbalage.owlforms.api.Form;
import hu.gbalage.owlforms.api.data.CompositeValue;
import hu.gbalage.owlforms.api.data.FieldData;
import hu.gbalage.owlforms.api.data.FieldValue;
import hu.gbalage.owlforms.api.data.FormData;
import hu.gbalage.owlforms.api.data.PrimitiveValue;
import hu.gbalage.owlforms.internal.derived.AllDerivedCalculator;
import hu.gbalage.owlforms.internal.derived.IDerivedCalculator;

/**
 * @author balage
 *
 */
public class FormDataSaver {

	final OWLFormsManager manager;
	
	final OWLDataFactory factory;
	
	final OWLReasoner reasoner;
	
	final IDerivedCalculator derived = new AllDerivedCalculator();
	
	public FormDataSaver(OWLFormsManager manager){
		this.manager = manager;
		this.factory = manager.getDataFactory();
		this.reasoner = manager.getReasoner();
	}
	
	private OWLOntologyChange saveDataValue(OWLIndividual individual, OWLDataProperty property, 
				PrimitiveValue value, OWLOntology ontology){
		OWLDataType type = factory.getOWLDataType(value.getType());
		OWLConstant constant = factory.getOWLTypedConstant(value.getValue(), type);
		manager.getLog().debug("typed value of "+value.getValue()+" in the type of "+type);
		return new AddAxiom(ontology, factory.getOWLDataPropertyAssertionAxiom(individual, property, constant));
	}
	
	private List<OWLOntologyChange> saveObjectValue(OWLIndividual individual, OWLObjectProperty property, 
			CompositeValue value, OWLOntology ontology, boolean imported){
		List<OWLOntologyChange> changes = new ArrayList<OWLOntologyChange>();
		
		if (!imported)
		changes.addAll(save(value.getCompositeData(), ontology));
		OWLIndividual object = factory.getOWLIndividual(value.getCompositeData().getURI());
		
		changes.add(new AddAxiom(ontology,factory.getOWLObjectPropertyAssertionAxiom(individual, property, object)));
		
		return changes;
	}
	
	private List<OWLOntologyChange> saveField(FieldData fdata, OWLOntology ontology,OWLIndividual individual){
		List<OWLOntologyChange> changes = new ArrayList<OWLOntologyChange>();
		
		Field field = fdata.getField();
		if (!field.isDerivative()) //the field is derivative, we won't allow it to be saved
		if (field.isPrimitive()){
			OWLDataProperty property = factory.getOWLDataProperty(field.getURI());
			for (FieldValue value : fdata.listValues()){
				changes.add(saveDataValue(individual, property, value.asPrimitiveValue(), ontology));
			}
		}else{
			OWLObjectProperty property = factory.getOWLObjectProperty(field.getURI());
			for (FieldValue value : fdata.listValues()){
				boolean imported = false;
				if (field.isComplex()) imported = field.asComplexField().isImported();
				changes.addAll(saveObjectValue(individual, property, value.asCompositeValue(), ontology,imported));
			}
		}
		
		return changes;
	}
	
	public List<OWLOntologyChange> save(FormData data, OWLOntology ontology){
		List<OWLOntologyChange> changes = new ArrayList<OWLOntologyChange>();
		Form form = data.getForm();
		//OWLOntologyManager oman = manager.getOntologyManager();
		OWLClass clazz = factory.getOWLClass(data.getForm().getURI());
		OWLIndividual indiviudal = factory.getOWLIndividual(data.getURI());
		
		//Assert individual to be an instance of the form class
		changes.add(new AddAxiom(ontology,factory.getOWLClassAssertionAxiom(indiviudal, clazz)));
		
		//Save fields
		for(Field field : form.listFields()){
			FieldData fdata = data.getData(field);
			changes.addAll(saveField(fdata,ontology,indiviudal));
		}
		
		return changes;
	}
	
	private FieldData findFieldData(FormData data, URI property){
		for(Field field : data.getForm().listFields()){
			//manager.getLog().debug("Comparing URIs: "+field.getURI()+" - "+property);
			if (field.getURI().equals(property)) return data.getData(field);
		}
		manager.getLog().error("Could not find FieldData of property "+property+" in form "+data.getForm().getURI());
		return null;
	}
	
	private boolean isInstanceOf(OWLIndividual individual, OWLClass clazz) throws OWLReasonerException{
		Set<Set<OWLClass>> clazzes = reasoner.getTypes(individual, true);
		for(Set<OWLClass> clzs : clazzes)
			for(OWLClass clz : clzs){
				if (clz.equals(clazz)) return true;
				if (reasoner.isSubClassOf(clz, clazz)) return true;
			}
		
		return false;
	}
	
	private Form findFormForIndividual(Form[] forms, OWLIndividual individual) throws OWLReasonerException{
		for(Form form : forms){
			OWLClass clazz = factory.getOWLClass(form.getURI());
			if (isInstanceOf(individual, clazz)) return form;
		}
		return null;
	}
	
	public FormData load(Form form, URI instance) throws OWLReasonerException{
		FormData result = manager.getFormsDataFactory().createEmptyData(form, instance);
		
		OWLIndividual individual = factory.getOWLIndividual(instance);
		
		//load data properties
		Map<OWLDataProperty, Set<OWLConstant>> data = reasoner.getDataPropertyRelationships(individual);
		for(OWLDataProperty property : data.keySet()){
			FieldData fdata = findFieldData(result, property.getURI());
			for(OWLConstant constant : data.get(property)) if (fdata != null){
				if (constant.isTyped()){
					URI typ = constant.asOWLTypedConstant().getDataType().getURI();
					FieldValue value = fdata.insertValue(typ);
					if (!value.isPrimitive()){
						manager.getLog().error("Error on getting a fieldvalue: "+fdata.getField().getURI()+" =?= "+property.getURI());
						manager.getLog().error(" Value is: "+constant.getLiteral()+" of the type "+typ);
					}
					value.asPrimitiveValue().setValue(constant.getLiteral());
				}else{
					fdata.insertValue(Constants.XSD_string).asPrimitiveValue().setValue(constant.getLiteral());
				}
			}
		}
		
		//load object properties
		Map<OWLObjectProperty, Set<OWLIndividual>> objects = reasoner.getObjectPropertyRelationships(individual);
		for(OWLObjectProperty property : objects.keySet()){
			FieldData fdata = findFieldData(result, property.getURI());
			for(OWLIndividual value : objects.get(property)) if (fdata != null){
				Form comform =findFormForIndividual(fdata.getField().asComplexField().getContainedForm(), value);
				FormData compdata = load(comform,value.getURI());
				fdata.insertValue(new CompositeValueImpl(compdata));
			}
		}
		
		for(Field f : form.listFields()){
			if(f.isDerivative()){
				String value = derived.calculate(result, f);
				if (value != null) result.getData(f).insertValue(f.getAcceptedTypes().iterator().next()).asPrimitiveValue().setValue(value);
			}
		}
		
		return result;
	}
	
}
